/**************************
 * Includes
 *
 **************************/


#include <stdio.h>
#include <string>
#include <fstream>
#include <vector>
//#include <new>
#include <ctime>
//#include <process.h>

#include <iostream>
//#include <fcntl.h>
//#include <io.h>

//#include <GL/glew.h>
//#include <GL/wglew.h>
//#include <GL/glaux.h>
//#include <GL/gl.h>


//#include "Camera.h"
#include "../Matrices_old.h"
#include "../GLRenderer.h"
#include "ViewModelWindow.h"
#include "../signalslot.h"
#include "tri_stripper.h"

#include "../GLMesh.h"

#include "globals.h"


#include "../leak.h"

#undef main

const float UPDATE_DELTA = 1000.0f/60.0f;


using namespace std;

/**************************
 * Function Declarations
 *
 **************************/

void Function();


cSignal< int, float, float > click_signal;
cSignal< int, float, float > move_signal;
cSignal< int, float, float > release_signal;
cSignal< int, vector<unsigned char>& > key_press_signal;



GLTexture g_checker_texture;

int g_num_brushes_done = 0;

float ccolor_table[4096][3];

bool view_update_needed = true;

void CompileFile(string, string);

GLStaticMesh mesh, mesh2;

/**************************
 * WinMain
 *
 **************************/

enum{
	COMPILE = 1,
	VIEW 	= 2,
};

int main(int argc, char* argv[])
{
	int mode = 0;
	string in_name, out_name, view_name;

	if(argc < 3)
	{
		cout << "error" << endl;
		return -1;
	}

	for(int i = 1; i < argc-1; i++)
	{
		if(strcmp(argv[i], "-v") == 0)
		{
			mode |= VIEW;
			view_name = argv[i+1];
		} else
		if(strcmp(argv[i], "-i") == 0)
		{
			mode |= COMPILE;
			in_name = argv[i+1];
		}else
		if(strcmp(argv[i], "-o") == 0)
		{
			mode |= COMPILE;
			out_name = argv[i+1];
		}
	}

	if(SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		//sdldie("Unable to initialize SDL");
	}

	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);

	SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
	SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
	SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
	SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, 8 );
	SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
	SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
	SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE,   32);
	SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL,   1);

	//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS,  0);
	//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,  4);

	g_prev_time = SDL_GetTicks();

	PRINT_UNFREED
	ViewModelWindow view_window;
	view_window.Show();

	//view_window.Show();

	
	Renderer::Init( &g_render_state );

	Renderer renderer(800, 600);
	g_renderer = &renderer;
	g_renderer->set_DrawFunc(Function);

		
	float cam_pos[3] = {25.0, 0.0, 0.0};
	float cam_target[3] = {0.0, 0.0, 0.0};
	float up[3] = {0.0, 1.0, 0.0};
	Camera cam(cam_pos, cam_target, up);
	cam.Perspective(45, 1.0f, 0.1f, 10000.0f);

	g_render_state.current_cam = &cam;

			
	for(int i = 0; i < 4096; i++)
	{
		ccolor_table[i][0] = ((float)(rand() % 256))/(256.0f);
		ccolor_table[i][1] = ((float)(rand() % 256))/(256.0f);
		ccolor_table[i][2] = ((float)(rand() % 256))/(256.0f);
	}

	g_checker_texture = Renderer::LoadTGATexture("textures/checker.tga");

	click_signal.Connect( view_window.click );
	move_signal.Connect( view_window.move );
	release_signal.Connect( view_window.release );
	key_press_signal.Connect( view_window.key_press );

	if(mode & COMPILE)
	{
		CompileFile(in_name, out_name);
	}

	if(mode & VIEW)
	{
		mesh.LoadFromFile(view_name);
	}else
	{
		g_quit = true;
	}

	//CompileFile("model.txt", "model.mesh");
	//CompileFile("model.txt", "model.mesh");


    while (!g_quit)
    {
    	FireSignals(click_signal, release_signal, move_signal, key_press_signal);

		g_cur_time = SDL_GetTicks();
		g_delta_time = g_cur_time - g_prev_time;
		if(g_delta_time > 200)
		{
			g_delta_time = 200;
		}
		g_prev_time = g_cur_time;

		g_time_acc += g_delta_time;



		while(g_time_acc >= UPDATE_DELTA)
		{
			cam.Move(move_vec, UPDATE_DELTA);

			mesh.Rotate(0.0f, 0.001f * UPDATE_DELTA, 0.0f);
			//mesh2.Rotate(0.0f, -0.001f * UPDATE_DELTA, 0.0f);
			//mesh2.Move(0.0f, 0.01f, 0.0f);

			g_time_acc -= UPDATE_DELTA;
		}

		cam.UpdatePlanes();

		view_window.MakeCurrent();

		if( view_update_needed )
		{

			view_window.SetupMainViewPort( cam );

			glClearColor(0.0, 0.5, 0.5, 0.0);

			g_renderer->Draw();

			glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

		}

		glClearColor(0.0, 0.0, 0.0, 0.0);
		glClear(GL_COLOR_BUFFER_BIT);


		glDisable(GL_DEPTH_TEST);

		g_renderer->SwitchProgram(SCREEN_DRAW_PROGRAM);

		view_window.Draw();


		view_window.Apply();
	}

	SDL_Quit();
	PRINT_UNFREED
	return 0;
}


vector<float> positions, normals, uvs;
vector<vector<unsigned short>> indices;

vector<vector<unsigned short>> strips;
vector<vector<unsigned short>> long_strips;

vector<string> materials;
vector<unsigned int> tex_ids;

void Function2() {

	lock_guard<mutex> render_lock(g_render_state.render_mtx);


	Renderer::SwitchProgram(MAIN_PROGRAM);

	Renderer::SwitchOwnTexture(DIFFUSEMAP_SLOT, g_checker_texture);

	float view_proj_mat[16];
	matrixMultiplyMM(view_proj_mat, (float*)Renderer::render_state->current_cam->projection_matrix(), (float*)Renderer::render_state->current_cam->view_matrix());

	glUniformMatrix4fv(Renderer::render_state->current_program->uniform(MVP_UNIFORM), 1, GL_FALSE, view_proj_mat);

	glUniform3f(Renderer::render_state->current_program->uniform(COL_UNIFORM), 1.0f, 1.0f, 1.0f);

	glEnableVertexAttribArray(Renderer::render_state->current_program->attribute(POSITION_ATTRIBUTE));
	glVertexAttribPointer(Renderer::render_state->current_program->attribute(POSITION_ATTRIBUTE), 3, GL_FLOAT, GL_FALSE, 0, &positions[0]);

	glEnableVertexAttribArray(Renderer::render_state->current_program->attribute(UV_ATTRIBUTE));
	glVertexAttribPointer(Renderer::render_state->current_program->attribute(UV_ATTRIBUTE), 2, GL_FLOAT, GL_FALSE, 0, &uvs[0]);

	//glEnableVertexAttribArray(Renderer::render_state->current_program->attribute(NORMAL_ATTRIBUTE));
	//glVertexAttribPointer(Renderer::render_state->current_program->attribute(NORMAL_ATTRIBUTE), 3, GL_FLOAT, GL_FALSE, 0, &normals[0]);

	//glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, &indices[0]);

	//glDrawElements(GL_TRIANGLE_STRIP, long_strip.size(), GL_UNSIGNED_SHORT, &long_strip[0]);
	/*for(unsigned int i = 0; i < long_strips.size(); i++)
	{
		glUniform3f(Renderer::render_state->current_program->uniform(COL_UNIFORM), color_table[i][0], color_table[i][1], color_table[i][2]);
		glDrawElements(GL_TRIANGLE_STRIP, long_strips[i].size(), GL_UNSIGNED_SHORT, &long_strips[i][0]);
	}*/

	for(unsigned int i = 0; i < strips.size(); i++)
	{
		glUniform3f(Renderer::render_state->current_program->uniform(COL_UNIFORM), ccolor_table[i][0], ccolor_table[i][1], ccolor_table[i][2]);
		glDrawElements(GL_TRIANGLE_STRIP, strips[i].size(), GL_UNSIGNED_SHORT, &strips[i][0]);
	}

	//glDisableVertexAttribArray(Renderer::render_state->current_program->attribute(NORMAL_ATTRIBUTE));
	glDisableVertexAttribArray(Renderer::render_state->current_program->attribute(UV_ATTRIBUTE));
	glDisableVertexAttribArray(Renderer::render_state->current_program->attribute(POSITION_ATTRIBUTE));

}

void Function() {

	lock_guard<mutex> render_lock(g_render_state.render_mtx);

	//mesh.Rotate(0.0f, 0.001f, 0.0f);

	Renderer::SwitchProgram(MAIN_PROGRAM);

	Renderer::SwitchOwnTexture(DIFFUSEMAP_SLOT, g_checker_texture);

	/*float view_proj_mat[16];
	matrixMultiplyMM(view_proj_mat, (float*)Renderer::render_state->current_cam->projection_matrix(), (float*)Renderer::render_state->current_cam->view_matrix());

	glUniformMatrix4fv(g_render_state.current_program->uniform(MVP_UNIFORM), 1, GL_FALSE, view_proj_mat);
*/
	glUniform3f(Renderer::render_state->current_program->uniform(COL_UNIFORM), 1.0f, 1.0f, 1.0f);

	mesh.Draw();

	//mesh2.Draw();

}

vector<char*> Tokenize( char* str, const char* delims ) {
	vector<char*> toks;
	char* tok = strtok( str, delims );
	while( tok != NULL )
	{
		toks.push_back( tok );
		tok = strtok( NULL, delims );
	}
	return toks;
}

void CompileFile(string in_file_name, string out_file_name){

	ifstream file(in_file_name);
	string str;
	unsigned int num_vertices, num_indices;
	//vector<float> positions, normals, uvs;
	//vector<unsigned int> indices;

	str.reserve(100);
	getline(file, str);
	num_vertices = atoi(str.c_str());
	positions.reserve(num_vertices*3);
	normals.reserve(num_vertices*3);
	uvs.reserve(num_vertices*2);
	getline(file, str);
	num_indices = atoi(str.c_str());
	//indices.reserve(num_indices);


	class MeshInfo{
	public:
		char name[32];
		float bbox_min[3];
		float bbox_max[3];
	}mesh_info;

	strcpy(mesh_info.name, "ModelName");
	mesh_info.bbox_min[0] = mesh_info.bbox_min[1] = mesh_info.bbox_min[2] = std::numeric_limits<float>::max();
	mesh_info.bbox_max[0] = mesh_info.bbox_max[1] = mesh_info.bbox_max[2] = -std::numeric_limits<float>::max();

	for(unsigned int i = 0; i < num_vertices; i++)
	{
		getline(file, str);
		char strr[128];
		strcpy(strr, str.c_str());
		vector<char*> toks = Tokenize(strr, " ");
		float 	x = atof(toks[0]),
				y = atof(toks[1]),
				z = atof(toks[2]);
		positions.push_back(x);
		positions.push_back(y);
		positions.push_back(z);

		mesh_info.bbox_min[0] = min(mesh_info.bbox_min[0], x);
		mesh_info.bbox_max[0] = max(mesh_info.bbox_max[0], x);
		mesh_info.bbox_min[1] = min(mesh_info.bbox_min[1], y);
		mesh_info.bbox_max[1] = max(mesh_info.bbox_max[1], y);
		mesh_info.bbox_min[2] = min(mesh_info.bbox_min[2], z);
		mesh_info.bbox_max[2] = max(mesh_info.bbox_max[2], z);

		normals.push_back(atof(toks[3]));
		normals.push_back(atof(toks[4]));
		normals.push_back(atof(toks[5]));
		uvs.push_back(atof(toks[6]));
		uvs.push_back(-atof(toks[7]));
	}

	unsigned int indices_index = -1;
	for(unsigned int i = 0; i < num_indices/3; i++)
	{
		getline(file, str);
		char strr[128];
		strcpy(strr, str.c_str());

		if(strr[0] > '9' || strr[0] < '0')
		{
			vector<char*> toks = Tokenize(strr, " ");
			materials.push_back(toks[0]);

			if(!indices.empty())
				indices[indices_index].shrink_to_fit();
			indices_index++;
			indices.push_back(vector<unsigned short>(0));
			i--;
			continue;
		}

		vector<char*> toks = Tokenize(strr, " \t");
		indices[indices_index].push_back(atoi(toks[0]));
		indices[indices_index].push_back(atoi(toks[1]));
		indices[indices_index].push_back(atoi(toks[2]));
	}

	indices[indices_index].shrink_to_fit();
	indices.shrink_to_fit();

	cout << endl << positions[0] << " " << positions[1] << " " << positions[2] << endl;
	cout << endl << normals[0] << " " << normals[1] << " " << normals[2] << endl;
	cout << endl << uvs[0] << " " << uvs[1] << endl;
	//cout << endl << indices[0] << " " << indices[1] << endl;
	cout << endl << materials.size() << endl;

	//vector<unsigned int> indices = indices;

	unsigned int strip_ndx = -1;
	for(auto& index_group : indices)
	{
		triangle_stripper::tri_stripper TriStripper(index_group);
		triangle_stripper::tri_stripper::primitives_vector PrimitivesVector;

		TriStripper.SetMinStripSize(0);
		TriStripper.SetCacheSize(64);
		//TriStripper.SetCacheSize(20480);
		TriStripper.Strip(&PrimitivesVector);

		cout << endl << "num_strips : " << PrimitivesVector.size() << endl;

		long_strips.push_back(vector<unsigned short>(0));
		strip_ndx++;

		for(auto& vec : PrimitivesVector)
		{
			if(vec.m_Type != 0x0005)
			{
				//cout << "qqqq" << endl;

				for(unsigned int i = 0; i < vec.m_Indices.size(); i += 3)
				{
					if(!long_strips[strip_ndx].empty())
					{
						long_strips[strip_ndx].push_back(long_strips[strip_ndx][long_strips[strip_ndx].size() - 1]);
						long_strips[strip_ndx].push_back(vec.m_Indices[i]);

						if((long_strips[strip_ndx].size() - 2)%2 != 0)
						{
							long_strips[strip_ndx].push_back(vec.m_Indices[i]);
							//cout << "qqqq" << endl;
						}
					}

					long_strips[strip_ndx].push_back(vec.m_Indices[i]);
					long_strips[strip_ndx].push_back(vec.m_Indices[i + 1]);
					long_strips[strip_ndx].push_back(vec.m_Indices[i + 2]);

					strips.emplace_back(&vec.m_Indices[i], &vec.m_Indices[i] + 3);
				}
			} else
			{
				if(!long_strips[strip_ndx].empty())
				{
					long_strips[strip_ndx].push_back(long_strips[strip_ndx][long_strips[strip_ndx].size() - 1]);
					long_strips[strip_ndx].push_back(vec.m_Indices[0]);

					if((long_strips[strip_ndx].size() - 2)%2 != 0)
					{
						long_strips[strip_ndx].push_back(vec.m_Indices[0]);
						//cout << "qqqq" << endl;
					}
				}
				long_strips[strip_ndx].insert(long_strips[strip_ndx].end(), vec.m_Indices.begin(), vec.m_Indices.end());

				strips.push_back(std::move(vec.m_Indices));
			}
		}
		long_strips[long_strips.size() - 1].shrink_to_fit();
	}

	vector<unsigned short> total_indices;
	class Strip{
	public:
		int index;
		int num_indices;
		Strip(int ndx, int num) : index(ndx), num_indices(num){}
	};
	vector<Strip> total_strips;
	for(auto& strip : long_strips)
	{
		total_strips.emplace_back(total_indices.size(), strip.size());
		total_indices.insert(total_indices.end(), strip.begin(), strip.end());
	}

	ofstream out_file(out_file_name, ios::out | ios::binary);

	out_file.write("STATIC_MESH\0", 12);

	enum{
		MESH_INFO_CHUNK = 0,
		VTX_ATTR_CHUNK,
		VTX_NDX_CHUNK,
		TEXTURES_CHUNK,
		STRIPS_CHUNK,
	};

	class ChunkPos{
	public:
		int offset;
		int length;
	};

	class Header{
	public:
		int num_chunks;
		ChunkPos p[5];
	}file_header;

	file_header.num_chunks = 5;
	size_t file_offset = 12 + sizeof(file_header);

	file_header.p[MESH_INFO_CHUNK].offset = file_offset;
	file_header.p[MESH_INFO_CHUNK].length = sizeof(mesh_info);

	file_offset += file_header.p[MESH_INFO_CHUNK].length;
	file_header.p[VTX_ATTR_CHUNK].offset = file_offset;
	file_header.p[VTX_ATTR_CHUNK].length = sizeof(float) * (positions.size()/3) * 8;

	file_offset += file_header.p[VTX_ATTR_CHUNK].length;
	file_header.p[VTX_NDX_CHUNK].offset = file_offset;
	file_header.p[VTX_NDX_CHUNK].length = sizeof(unsigned short) * total_indices.size();

	file_offset += file_header.p[VTX_NDX_CHUNK].length;
	file_header.p[TEXTURES_CHUNK].offset = file_offset;
	file_header.p[TEXTURES_CHUNK].length = 64 * materials.size();

	file_offset += file_header.p[TEXTURES_CHUNK].length;
	file_header.p[STRIPS_CHUNK].offset = file_offset;
	file_header.p[STRIPS_CHUNK].length = sizeof(Strip) * total_strips.size();


	out_file.write((char*)&file_header, sizeof(file_header));
	out_file.write((char*)&mesh_info, sizeof(mesh_info));

	for(unsigned int i = 0; i < positions.size()/3; i++)
	{
		out_file.write((char*)&positions[i*3], sizeof(float) * 3);
		out_file.write((char*)&normals[i*3], sizeof(float) * 3);
		out_file.write((char*)&uvs[i*2], sizeof(float) * 2);
	}

	out_file.write((char*)&total_indices[0], sizeof(unsigned short) * total_indices.size());

	for(auto& str : materials)
	{
		char name[64];
		strcpy(name, "textures/");
		strcat(name, str.c_str());
		//strcpy(name, str.c_str());
		strcat(name, ".tga");
		out_file.write((char*)&name[0], sizeof(char) * 64);
	}

	out_file.write((char*)&total_strips[0], sizeof(Strip) * total_strips.size());

}
